﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Globalization;
using Watson.TemporaryFilesDeleter.Util;

namespace Watson.TemporaryFilesDeleter.UI.Windows
{
    public partial class Form1 : Form
    {
        #region Delegates

        /// <summary>
        /// This delegate enables asynchronous calls for adding an entry to the
        /// log (list view).
        /// </summary>
        /// <param name="text">
        /// The text to add to the log.
        /// </param>
        delegate void AddLogEntryCallback(LogItem logItem);

        #endregion
        
        #region Private Fields

        /// <summary>
        /// A reference to the last background worker created for obtaining
        /// information about the selected temporary files location.
        /// </summary>
        /// <remarks>
        /// Keeping track of this allows us to cancel previous requests if we
        /// make a new one before the previous one has finished.
        /// </remarks>
        BackgroundWorker lastTemporaryFilesLocationInformationBackgroundWorker;

        /// <summary>
        /// Is the application busy, i.e. is there an active file deletion
        /// operation taking place?
        /// </summary>
        private bool isBusy;

        #endregion

        #region Event Handlers

        private void btnDeleteFiles_Click(object sender, EventArgs e)
        {
            /*
             * Setup a new FileDeleter.
             */
            FileDeleter fileDeleter = new FileDeleter()
            {
                Folder = this.cboTemporaryFilesLocation.Text
            };


            /*
             * Add filters according to user selection.
             */
            if (this.rbtnDeleteOldFiles.Checked)
            {
                /*
                 * Calculate a minimum last modified date based on user input.
                 */
                int days = Convert.ToInt32(cboDeleteOldFilesDays.Text);

                DateTime minimumLastModifiedDate = 
                    DateTime.Now.AddDays(0 - days);

                IFileFilter fileAgeFilter = 
                    new LastModifiedFileFilter(minimumLastModifiedDate);

                fileDeleter.Filters.Add(fileAgeFilter);
            }

            if (this.rbtnDeleteLargeFiles.Checked)
            {
                /*
                 * Calculate a minimum file size based on user input. Convert 
                 * input (in megabytes) to bytes.
                 */
                int megabytes = Convert.ToInt32(cboDeleteLargeFilesMBs.Text);

                long minimumFileSize = megabytes * 1024 * 1024;

                IFileFilter fileSizeFilter =
                    new FileSizeFileFilter(minimumFileSize);

                fileDeleter.Filters.Add(fileSizeFilter);
            }


            /*
             * Create a new background worker on which to run the deletion
             * operation.
             */
            BackgroundWorker worker = new BackgroundWorker()
            {
                WorkerReportsProgress = true
            };


            /*
             * Set isBusy flag, put the form into a state that indicates to the
             * user it is busy.
             */
            this.prgActivity.Maximum     = 100;
            this.prgActivity.Value       = 0;
            this.lblCurrentActivity.Text = "";
            this.isBusy                  = true;
            this.UseWaitCursor           = true;

            this.SetControlEnabledState();

            
            /*
             * Set up the DoWork event, this is where the long running task is
             * run.
             */
            worker.DoWork +=
                new DoWorkEventHandler((doWorkSender, doWorkEventArgs) =>
                {
                    /*
                     * Respond to CurrentActivityChanged event by reporting
                     * progress.
                     */
                    fileDeleter.CurrentActivityChanged +=
                        new EventHandler<CurrentActivityChangedEventArgs>(
                            (currentActivityChangedSender,
                                currentActivityChangedEventArgs) =>
                        {
                            worker.ReportProgress(fileDeleter.ProgressPercentage);
                        });
                    

                    /*
                     * Respond to ProgressChanged event by reporting progress.
                     */
                    fileDeleter.ProgressChanged +=
                        new EventHandler<ProgressChangedEventArgs>(
                            (progressChangedSender,
                             progressChangedEventArgs) =>
                        {
                            worker.ReportProgress(
                                progressChangedEventArgs.ProgressPercentage);
                        });
                    

                    /*
                     * Begin the destruction!
                     */
                    fileDeleter.Execute();
                });


            /*
             * Setup the RunWorkerCompleted event. This is where we restore the
             * form to it's "not busy" look and respond to any errors.
             */
            worker.RunWorkerCompleted += 
                new RunWorkerCompletedEventHandler(
                    (runWorkerCompletedSender, 
                     runWorkerCompletedEventArgs) =>
                {
                    if (runWorkerCompletedEventArgs.Error != null)
                    {
                        // TODO: Handle error.
                        MessageBox.Show(runWorkerCompletedEventArgs.Error.Message);
                    }
                    
                    /*
                     * Clear isBusy flag, return form to enabled state.
                     */
                    this.lblCurrentActivity.Text = "";
                    this.isBusy                  = false;
                    this.UseWaitCursor           = false;

                    this.SetControlEnabledState();
                });


            /*
             * Setup the ProgressChanged event. This is where we update the
             * form showing progress to the user.
             */
            worker.ProgressChanged += 
                new ProgressChangedEventHandler(
                    (progressChangedSender, 
                     progressChangedEventArgs) =>
                {
                    this.prgActivity.Value = 
                        progressChangedEventArgs.ProgressPercentage;

                    this.lblCurrentActivity.Text =
                        fileDeleter.CurrentActivity;

                    System.Diagnostics.Debug.WriteLine(
                        "Progress: {0,5:P0}, CurrentActivity: \"{1}\"",
                        (progressChangedEventArgs.ProgressPercentage / 100f),
                        fileDeleter.CurrentActivity);
                });


            /*
             * Begin the long running deletion operation.
             */
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Handles the Click event of the btnViewFiles button. Opens Window
        /// Explorer showing the currently entered directory.
        /// </summary>
        /// <param name="sender">
        /// The object that sent the event.
        /// </param>
        /// <param name="e">
        /// The event arguments.
        /// </param>
        private void btnViewFiles_Click(object sender, EventArgs e)
        {
            string path = cboTemporaryFilesLocation.Text;

            if (String.IsNullOrWhiteSpace(path))
            {
                return;
            }
            
            ExplorerUtil.OpenExplorer(path);
        }

        private void cboTemporaryFilesLocation_SelectedIndexChanged(
            object sender, EventArgs e)
        {
            /*
             * If we have a previous background worker and it's still busy, 
             * cancel it. We're not interested in the result any more.
             */
            if (lastTemporaryFilesLocationInformationBackgroundWorker != null
                &&
                lastTemporaryFilesLocationInformationBackgroundWorker.IsBusy)
            {
                System.Diagnostics.Debug.WriteLine("Cancelling...");
                lastTemporaryFilesLocationInformationBackgroundWorker
                    .CancelAsync();
            }


            /*
             * Create a new background worker on which to obtain the file
             * location information.
             */
            BackgroundWorker worker = new BackgroundWorker()
            {
                WorkerSupportsCancellation = true
            };

            /*
             * Keep track of the last created background worker to allow
             * cancellations.
             */
            lastTemporaryFilesLocationInformationBackgroundWorker = worker;


            /*
             * Put the temporary files location display into a state that
             * indicates to the user it's busy.
             */
            this.lblTemporaryFilesLocationInformation.UseWaitCursor = true;
            
            this.lblTemporaryFilesLocationInformation.Text =
                String.Format(CultureInfo.CurrentUICulture,
                              "{0,11} folders\r\n"
                            + "{0,11} files\r\n"
                            + "{0,11} bytes",
                              "<busy>");


            /*
             * Obtain the current path for later.
             */
            string path = cboTemporaryFilesLocation.Text;


            /*
             * Setup up the worker. This is where the long running work is
             * done.
             */
            worker.DoWork +=
                new DoWorkEventHandler((doWorkSender, doWorkEventArgs) =>
                {
                    string info = GetFilesLocationInformation(path);

                    doWorkEventArgs.Result = info;

                    if (worker.CancellationPending)
                    {
                        doWorkEventArgs.Cancel = true;
                    }
                });


            /*
             * Setup the worker's completed event. This is where we use the
             * result of the long running work.
             */
            worker.RunWorkerCompleted += 
                new RunWorkerCompletedEventHandler(
                    (runWorkerCompletedSender,
                     runWorkerCompletedEventArgs) =>
                {
                    if (runWorkerCompletedEventArgs.Cancelled)
                    {
                        System.Diagnostics.Debug.WriteLine("Cancelled.");
                        return;
                    }

                    this.lblTemporaryFilesLocationInformation.Text =
                        runWorkerCompletedEventArgs.Result.ToString();

                    this.lblTemporaryFilesLocationInformation.UseWaitCursor
                        = false;
                }
            );


            /*
             * Begin the long running work.
             */
            worker.RunWorkerAsync();
        }
        
        private void Instance_LogItemWritten(object sender, 
            LogItemWrittenEventArgs e)
        {
            AddLogEntry(e.LogItem);
        }

        private void lvwLog_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == (char)Keys.Return)
            {
                this.ExpandSelectedLogItem();

                e.Handled = true;
            }
        }

        private void lvwLog_MouseDoubleClick(object sender, 
            MouseEventArgs e)
        {
            this.ExpandSelectedLogItem();
        }

        private void optionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            using (var optionsEditor = new OptionsEditor())
            {
                optionsEditor.ShowDialog(this);
            }
        }
        
        private void rbtnDeleteOldFiles_CheckedChanged(object sender, 
            EventArgs e)
        {
            SetControlEnabledState();
        }
        
        private void rbtnDeleteLargeFiles_CheckedChanged(object sender, 
            EventArgs e)
        {
            SetControlEnabledState();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Adds an entry to the log (list view).
        /// </summary>
        /// <param name="text">
        /// The text to add to the log.
        /// </param>
        private void AddLogEntry(LogItem logItem)
        {
            if (this.lvwLog.InvokeRequired)
            {
                AddLogEntryCallback d = new AddLogEntryCallback(AddLogEntry);
                this.Invoke(d, new object[] { logItem });
            }
            else
            {
                string[] items = new string[2]
                {
                    logItem.EnteredDateTime.ToString(),
                    logItem.Text
                };

                string imageKey = logItem.LogLevel.ToString();

                ListViewItem listViewItem = new ListViewItem(items, imageKey)
                {
                    Tag = logItem
                };

                this.lvwLog.Items.Add(listViewItem);
            }
        }

        /// <summary>
        /// Assigns event handlers.
        /// </summary>
        private void AttachEventHandlers()
        {
            // Listen out for new log items being written.
            Log.Instance.LogItemWritten += this.Instance_LogItemWritten;
        }

        /// <summary>
        /// Displays the selected log item (if any) in a message box.
        /// </summary>
        private void ExpandSelectedLogItem()
        {
            if (lvwLog.SelectedItems.Count == 0)
                return;

            ListViewItem selectedItem = lvwLog.SelectedItems[0];

            if (selectedItem.Tag == null)
                return;

            LogItem logItem = selectedItem.Tag as LogItem;

            if (logItem == null)
                return;

            string text = String.Format(CultureInfo.CurrentCulture,
                                        "{0}\r\n\r\n{1}",
                                        logItem.EnteredDateTime,
                                        logItem.Text);

            string caption = "Log Item";

            MessageBoxIcon icon;

            switch (logItem.LogLevel)
            {
                case LogLevel.Information:
                    icon = MessageBoxIcon.Information;
                    break;
                case LogLevel.Warning:
                    icon = MessageBoxIcon.Warning;
                    break;
                case LogLevel.Error:
                    icon = MessageBoxIcon.Error;
                    break;
                case LogLevel.Debug:
                    icon = MessageBoxIcon.Question;
                    break;
                default:
                    icon = MessageBoxIcon.None;
                    break;
            }

            MessageBox.Show(text, caption, MessageBoxButtons.OK, icon);
        }
        
        /// <summary>
        /// Obtains information about the specified temporary files location as
        /// a formatted string suitable for display.
        /// </summary>
        /// <param name="path">
        /// Path to the temporary files location for which to obtain 
        /// information.
        /// </param>
        /// <returns>
        /// System.String
        /// <para>
        /// Formatted string containing information about the specified
        /// temporary files location suitable for display.
        /// </para>
        /// </returns>
        private string GetFilesLocationInformation(string path)
        {
            FilesLocationInfo locationInfo = 
                new FilesLocationInfo(path);

            string fileSize = String.Format(new FileSizeFormatProvider(),
                                            "{0:fs}",
                                            locationInfo.SizeOfFiles);

            // Split "2.56 MB" to "2.56" and "MB".
            string[] fileSizeComponents = fileSize.Split(new char[] { ' ' });

            string format = "{0,11:N0} folders\r\n"
                          + "{1,11:N0} files\r\n"
                          + "{2,11} {3}";

            string info = String.Format(CultureInfo.CurrentUICulture,
                                        format,
                                        locationInfo.NumberOfFolders,
                                        locationInfo.NumberOfFiles,
                                        fileSizeComponents[0],
                                        fileSizeComponents[1]);

            return info;
        }
        
        /// <summary>
        /// Initialises any image lists on the form.
        /// </summary>
        private void InitialiseImageLists()
        {
            /*
             * Setup icons used for log view.
             */
            imglstLogIcons.Images.Add("Information", 
                Properties.Resources.Information16);
            
            imglstLogIcons.Images.Add("Warning", 
                Properties.Resources.Warning16);
            
            imglstLogIcons.Images.Add("Error", 
                Properties.Resources.Error16);
        }

        /// <summary>
        /// Populates the various combo boxes on the form with suitable values.
        /// </summary>
        private void PopulateComboBoxes()
        {
            // Provide options for deleting files between 1 and 30 days old.
            cboDeleteOldFilesDays.Items.Clear();

            for (int days = 1; days < 30; days++)
            {
                cboDeleteOldFilesDays.Items.Add(days);
            }


            // Provide options for deleting files between 1MB and 1GB in size.
            cboDeleteLargeFilesMBs.Items.Clear();

            int[] fileSizes = new int[]
            {
                1,     //    1 MB
                2,     //    2 MB
                3,     //    3 MB
                4,     //    4 MB
                5,     //    5 MB
                6,     //    6 MB
                7,     //    7 MB
                8,     //    8 MB
                9,     //    9 MB
                10,    //   10 MB
                15,    //   15 MB
                20,    //   20 MB
                25,    //   25 MB
                30,    //   30 MB
                35,    //   35 MB
                40,    //   40 MB
                45,    //   45 MB
                50,    //   50 MB
                55,    //   55 MB
                60,    //   60 MB
                70,    //   70 MB
                80,    //   80 MB
                90,    //   90 MB
                100,   //  100 MB
                150,   //  150 MB
                200,   //  200 MB
                250,   //  250 MB
                300,   //  300 MB
                400,   //  400 MB
                512,   //  512 MB
                768,   //  768 MB
                1024   //    1 GB
            };

            foreach (var fileSize in fileSizes)
            {
                cboDeleteLargeFilesMBs.Items.Add(fileSize);
            }


            // Provide temporary files locations.
            cboTemporaryFilesLocation.Items.Clear();

            var temporaryFilesLocations = 
                new TemporaryFilesLocationsProvider();

            foreach (var location in temporaryFilesLocations.StandardLocations)
            {
                cboTemporaryFilesLocation.Items.Add(location);
            }
        }
        
        /// <summary>
        /// Sets the enabled state of all controls on the form with regard to
        /// whether there action can currently be performed, etc.
        /// </summary>
        private void SetControlEnabledState()
        {
            /*
             * If there is an active file deletion operation taking place,
             * disable majority of controls.
             */
            cboTemporaryFilesLocation.Enabled = !isBusy;

            rbtnDeleteAllFile.Enabled         = !isBusy;
            rbtnDeleteOldFiles.Enabled        = !isBusy;
            rbtnDeleteLargeFiles.Enabled      = !isBusy;

            cboDeleteOldFilesDays.Enabled     = !isBusy;
            cboDeleteLargeFilesMBs.Enabled    = !isBusy;

            btnDeleteFiles.Enabled            = !isBusy;

            btnViewFiles.Enabled              = !isBusy;


            /*
             * If we're not busy, enable these sub-selections based on other
             * user choices.
             */
            if (!isBusy)
            {
                /*
                 * May only select the age of files to be deleted if deleting 
                 * files by age.
                 */
                cboDeleteOldFilesDays.Enabled  = rbtnDeleteOldFiles.Checked;


                /*
                * May only select the size of files to be deleted if deleting
                * files by size.
                */
                cboDeleteLargeFilesMBs.Enabled = rbtnDeleteLargeFiles.Checked;
            }
        }

        /// <summary>
        /// Sets the initial view state of the various controls on the form.
        /// </summary>
        private void SetInitialViewState()
        {
            // Default to the first temporary files location.
            cboTemporaryFilesLocation.SelectedIndex = 0;
            // TODO: Better - select the last used item.
            

            // Default to deleting files older than one week.
            rbtnDeleteOldFiles.Checked = true;
            cboDeleteOldFilesDays.Text = "7";


            // Default the large files size to 10MB.
            cboDeleteLargeFilesMBs.Text = "10";
        }

        #endregion

        #region Public Constructors

        /// <summary>
        /// Initialises a new instance of the TODO form.
        /// </summary>
        //TODO: Rename the form from Form1.
        public Form1()
        {
            InitializeComponent();

            InitialiseImageLists();
            PopulateComboBoxes();
            SetInitialViewState();
            SetControlEnabledState();
            AttachEventHandlers();
        }

        #endregion
    }
}
